package com.faya.green.config.shiro;

import com.faya.green.customize.MyRealm;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 用户 ：LX
 * 创建时间： 2018/7/10. 16:35
 * 地点：广州
 * 目的: shiro配置
 * 结果：
 */
@Configuration
public class ShiroConfig {

    private static Logger log = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     * 对shiro的拦截器进行注入
     * <p>
     * securityManager:
     * 所有Subject 实例都必须绑定到一个SecurityManager上,SecurityManager 是 Shiro的核心，初始化时协调各个模块运行。然而，一旦 SecurityManager协调完毕，
     * SecurityManager 会被单独留下，且我们只需要去操作Subject即可，无需操作SecurityManager 。 但是我们得知道，当我们正与一个 Subject 进行交互时，实质上是
     * SecurityManager在处理 Subject 安全操作
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 没有登录的用户请求需要登录的页面时自动跳转到登录页面。springboot项目会自动去templates 下找。如果不设置默认会自动寻找Web工程根目录下的"/user.jsp"页面
        // 注意：这里的配置的不是模板文件的路径，而是失败后请求的controller路径
        shiroFilterFactoryBean.setLoginUrl("/user/loginUserUrl");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/user/indexUrl.php");
        // 未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuthorizedUrl");


        //拦截器，配置访问权限 必须是LinkedHashMap，因为它必须保证有序
        //过滤链定义，从上向下顺序执行，一般将 /**放在最为下边 --> : 这是一个坑，一不小心代码就不好使了
        Map<String, String> filterMap = new LinkedHashMap<String, String>();

        // 配置不会被拦截的链接 顺序判断
        filterMap.put("/static/**", "anon");
        //登录注册不拦截
        filterMap.put("/user/regUserUrl", "anon");
        //静态资源不拦截
        filterMap.put("/js/*", "anon");
        filterMap.put("/common/*", "anon");

        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了，可以用正常的拦截器正常去拦截，不一定非要这个拦截 filterMap.put("/user/logout.php", "logout");


        /*其他资源都需要认证  authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址  anon:所有url都都可以匿名访问，这有一个坑，如果这个位置
        的拦截器是放在下面的 所有的html后缀结尾的shiro都拦截 后面，就会被匹配成功，导致提前拦截，从而记住我无法实现，故而要放在前面。测试方式，登录成功后，就可以
         关闭浏览器，再次打开，直接用主页的地址就可以进入到页面，要想限定记住我的范围，配置下面的拦截器即可
         */
        filterMap.put("/main/indexUrl.php", "user");

        //所有的html后缀结尾的shiro都拦截
        filterMap.put("/**/*.php", "authc");



        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        log.debug("成功将shiro拦截器注入=============");
        return shiroFilterFactoryBean;
    }

    /**
     * securityManager 核心配置
     *
     * @return
     */
    @Bean
    public org.apache.shiro.mgt.SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        //设置自定义的realm
        defaultWebSecurityManager.setRealm(myRealm());
        log.debug("成功将自定义 realm 注入===============");

        //将缓存对象注入到SecurityManager中
        defaultWebSecurityManager.setCacheManager(ehCacheManager());

        //注入记住我管理器
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        return defaultWebSecurityManager;
    }


    /**
     * 自定义的realm
     *
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }


    /**
     * 开启shiro 的AOP注解支持
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        log.debug("开启shiro注解支持 ===============");
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * shiro缓存管理器
     * 1 添加相关的maven支持
     * 2 注册这个bean，将缓存的配置文件导入
     * 3 在securityManager 中注册缓存管理器，之后就不会每次都会去查询数据库了，相关的权限和角色会保存在缓存中，但需要注意一点，更新了权限等操作之后，需要及时的清理缓存
     */
    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache.xml");
        return cacheManager;
    }


    /**
     * cookie对象
     * 该方法是设置Cookie的生成模版，比如cookie的name，cookie的有效时间等等
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //这个参数就是cookie的名称，对应前端checkbox的name
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // 设为true后，只能通过http访问，javascript无法访问
        // 防止xss读取cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //记住我的cookie生效时间 30天， 单位秒
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }

    /**
     * cookie管理对象;记住我功能
     * rememberMeManager()方法是生成rememberMe管理器,需要加入核心 securityManager 中
     * 其实这里可以不用配置，这样shiro就会使用默认的配置
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //设置cookie对象，生成cookie模板
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }



}
